/*
Provides the basic gpio read/write functionality

author: anthony kulis
date: 3/6/2011
version 0.0
*/
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#include "gpio.h";


//turn off for production
#define DEBUG


/**
* @function popen
* used to validate input and initialize a port.
* unlike init_gpio, the developer can simply use strings 
* to intialize. 
*
* Valid inputs:
*	char *header: 
*		"DIO1", "dio1", "DIO2", "dio2", "LCD", "lcd", "ONBOARD_LEDS", "onboard_leds"
*
*	char port:
*		for dio1: 'b','B','c','C','f','F',NULL (defaults to port b)
*		for dio2: NULL
*		for lcd: 'a','A',NULL (defaults to port a)
*		for led: NULL
*
*	int DIRECTION: 
*		READ|WRITE (note you cannot read onboard_leds)
*
* @param char* The name of the header to initialized.
* @param char* The name of the port to be used. Note that
* this port must belong to the header. If a header only has
* one port, then this is ignored.
* @return On success, a struct *header_mapping. On faiure, NULL
* @author anthony kulis
* @version 0.0
*/
void* popen(char* header, char port, int DIRECTION){

	//have to do something
	if(DIRECTION != READ && DIRECTION != WRITE) return NULL;

	//first, validity check
	if(!strncmp(header, "DIO1", strlen(header)) || !strncmp(header, "dio1", strlen(header))){
		
		//dio1 can have ports: b, c, f
		switch (port){
		
			//null defaults to port b
			case '\0':
			case 'b':
			case 'B': return init_gpio(GPIO_BASE + PORT_B_OFFSET, DIRECTION);
				   			
			case 'c':
			case 'C': return init_gpio(GPIO_BASE + PORT_C_OFFSET, DIRECTION);
			
			case 'f':
			case 'F': return init_gpio(GPIO_BASE + PORT_F_OFFSET, DIRECTION);
			
			//must have a port for dio1
			default: return NULL;
		
		}	
	
	
	}
	
	else if(!strncmp(header, "DIO2", 4) || !strncmp(header, "dio2", 4))
	
		//dio2 has no choice
		return (port != NULL ? NULL : init_gpio(XDIO_BASE, DIRECTION);	
	
	}
	
	else if(!strncmp(header, "LCD", strlen(header)) || !strncmp(header, "lcd", strlen(header))){
	
	
		//lcd has no choice (must pass A|a|NULL)
		switch(port){
		
			case '\0':
			case 'a':
			case 'A': init_gpio(GPIO_BASE + PORT_A_OFFSET, DIRECTION);
			
			default: return NULL;
		
		}
	
	}
	
	else if(!strncmp(header, "ONBOARD_LEDS", stlen(header)) || !strncmp(header, "onboard_led", strlen(header))){
	
		//led have no choice in ports
		retrun (port != NULL ? NULL : init_gpio(GPIO_BASE + ONBOARD_LED_OFFSET, READ);
	
	}
	
	return NULL;



}


/**
* used to validate input and initialize a port. 
* @param char* The name of the header to initialized.
* @param char* The name of the port to be used. Note that
* this port must belong to the header. If a header only has
* one port, then this is ignored.
* @return On success, a struct *header_mapping. On faiure, NULL
* @author anthony kulis
* @version 0.0
*/
void* init_gpio(char *address, int DIRECTION){

	//since there are only a handfull addressing schemes (GPIO_BASE is only for offsets)
	if(strncmp(address, XDIO_BASE, strlen(address)) &&
	   strncmp(address, DIO1_PORT_B, strlen(address)) &&
	   strncmp(address, DIO1_PORT_C, strlen(address)) &&
	   strncmp(address, DIO1_PORT_F, strlen(address)) &&
	   strncmp(address, LCD_PORT_A, strlen(address)) &&
	   strncmp(address, ONBOARD_LEDS, strlen(address))) return NULL;
	   
	   
	//now check direction
	if(DIRECTION != READ && DIRECTION != WRITE) return NULL;
	
	//onboard leds can only write
	if(!strncmp(address, ONBOARD_LEDS, strlen(address)) && DIRECTION == READ) return NULL;
	
	//initialize vectors
	if(fd == NULL && (fd = malloc(sizeof(int) * FD_TABLE_SIZE)) == NULL){
		fprintf(stderr, "Error in allocating space for the file descriptor table: %s\n", strerr(errno));
		return NULL;
	} 
	
	if(maps == NULL){
		if(maps = malloc(sizof(char) * MMAP_TABLE_SIZE) == NULL){
			fprintf(stderr, "Error in allocating space for the mmap reference table: %s\n", strerr(errno));
			return NULL;
		
		}
		else memset(maps, '\0', MMAP_TABLE_SIZE);
	}
	
	//realloc if needed (i dont expect the mmap size to ever go larger than 2 (one for xdio and one for gpio)
	//and since the initial size if 4 (including ending \0), then we should have an easy check by using 
	//one more extra byte w/o having to do extra work (means I am using the second to last \0 as a sentinel)
	if(strlen(maps) == MMAP_TABLE_SIZE - 1){
		if(maps = realloc(maps, sizeof(char) * MMAP_TABLE_SIZE) == NULL){
			fprintf(stderr, "Unable to allocating more space for the mmap reference table: %s\n", strerr(errno));
			return NULL;
		}
		else{
			int i = strlen(maps) + 1;
			
			//fill the array with nulls 
			//(remember, 4 cells gives us 2 maps, 
			//allocating 4 more means we have 3 maps, 1 old null, 
			//and 4 unitialized cells)
			for(; i < strlen(maps) + 1 + MMAP_TABLE_SIZE; i++) maps[i] = '\0';
		}
		
	
	}
	
	
	//since we now have memory allocated, lets intialize the fd
	if(!strncmp(address, XDIO_BASE, strlen(address)) && (fd[1] = open("/dev/mem", O_RDWR) == -1){
		fprintf(stderr, "Error opening file \"/dev/mem\" for XDIO: %s\n", strerr(errno));
		return NULL;
	}
	else if(fd[0] = open("/dev/mem", O_RDWR) == -1){
		fprintf(stderr, "Error opening file \"/dev/mem\" for GPIO: %s\n", strerr(errno));
		return NULL;
	}
	
	//get mmap started
	if(maps[strlen(maps)] = mmap(PAGE_START, getpagesize(), PROT_READ|PROT_WRITE,  MAP_SHARED, 
		(strncmp(address, "XDIO_BASE", strlen(address)) ? fd[0] : fd[1]), address) == MAP_FAILED){
			fprintf(stderr, "Error mapping address: %s: %s\n", address, strerr(errno));
			return NULL;		
	}
	
	

	
	//special circumstance for leds	
	if(!strncmp(address, "ONBOARD_LEDS", strlen(address))){
		
		//create
		struct onboard_led_mappings olm;
		
		//init
		olm.led_0 = 0;
		olm.led_1 = 0;
		
		return &olm;
	
	}
	
	
	//start creating the return struct
	struct header{
		
		struct header_mapping map;
		struct header_mapping direction;
		struct header_mapping debounce;
	
	
	};
	
	//assign the requested address
	retval.map = (unsigned int *)(address);
	
	//now find the ddr to that address
	switch (address){
	
		case XDIO_BASE: 	retval.direction = (unsigned int *)NULL; //handle later
					retval.debounce = (unsigned int *)NULL; //handle later
				  	break;
				  	
		case DIO1_PORT_B: 	retval.direction = (unsigned int *)DIO1_PORT_B_DR;
				    	retval.debounce = (unsigned int *)PORT_B_DEBOUNCE;
				    	break;
				    	
		case DIO1_PORT_C:	retval.direction = (unsigned int *)DIO1_PORT_C_DR;
					retval.debounce = (unsigned int *)PORT_C_DEBOUNCE;
					break;
					
		case DIO1_PORT_F:	retval.direction = (unsigned int *)DIO1_PORT_F_DR;
					retval.debounce = (unsigned int *)PORT_F_DEBOUNCE;
					break;
					
		case LCD_PORT_A: 	retval.direction = (unsigned int *)LCD_PORT_A_DR;
					retval.debounce = (unsigned int *)PORT_A_DEBOUNCE;
					break;

		default: 		return NULL;
			
	
	};
	
	//clear the registers (of course, the user can change): - not sure if this is the correct way yet
	retval.map.*pin_0 = 0;
	retval.map.*pin_1 = 0;
	retval.map.*pin_2 = 0;
	retval.map.*pin_3 = 0;
	retval.map.*pin_4 = 0;
	retval.map.*pin_5 = 0;
	retval.map.*pin_6 = 0;
	retval.map.*pin_7 = 0;
	
	//set the default direction
	retval.direction.*pin_0 = DIRECTION;
	retval.direction.*pin_1 = DIRECTION;
	retval.direction.*pin_2 = DIRECTION;
	retval.direction.*pin_3 = DIRECTION;
	retval.direction.*pin_4 = DIRECTION;
	retval.direction.*pin_5 = DIRECTION;
	retval.direction.*pin_6 = DIRECTION;
	retval.direction.*pin_7 = DIRECTION;
	
	//now if reading, debounce
	if(!DIRECTION){
		retval.debounce.*pin_0 = DIRECTION;
		retval.debounce.*pin_1 = DIRECTION;
		retval.debounce.*pin_2 = DIRECTION;
		retval.debounce.*pin_3 = DIRECTION;
		retval.debounce.*pin_4 = DIRECTION;
		retval.debounce.*pin_5 = DIRECTION;
		retval.debounce.*pin_6 = DIRECTION;
		retval.debounce.*pin_7 = DIRECTION;
	
	}
	
	//think that is it
	return &retval;	
	
	

}